昨天從重放攻擊、時序攻擊與競態條件,說明驗證流程不能只檢查「資料是否正確」,還要確認它是否屬於這次流程、是否已經使用過,以及狀態更新與比較過程是否安全。
但這些防護都有成本:密碼雜湊需要運算資源,Token 驗證可能需要查詢伺服器,權限判斷與防重放紀錄也需要讀寫資料。如果每一層都加檢查,登入與 API 會不會越來越慢?若為了速度把結果快取起來,又會不會讓已經撤銷的權限繼續生效?
本篇的核心不是「安全與效能只能選一個」,而是:先定義不能省略的安全要求,再找出哪些成本可以透過參數、快取與架構設計來降低。
今天內容涵蓋:
登入變慢時,最容易出現的念頭是「這個檢查可以拿掉嗎?」。但密碼雜湊是在抵抗外洩後的猜測,簽章驗證是在確認資料沒被篡改,權限查詢是在確認這個人現在還能不能做這件事。拿掉它們確實會變快,只是那不叫優化,而是改了系統願意相信什麼。
接下來的雜湊成本、Token 驗證、快取與限流,每一項都有不同做法,也沒有一組套到哪裡都對的數字;真正決定怎麼選的,是這個系統自己的風險與需求。
攻擊者取得密碼雜湊資料後,可以在自己的設備上不斷猜測,不必再經過網站的登入端點;這種 離線猜測 不受網站登入限流的限制。因此密碼雜湊需要讓每一次猜測都付出足夠的 CPU 或記憶體成本,Argon2id 等演算法就是為此設計的。
但同樣的成本也會出現在合法登入上。如果參數設得太高,大量同時登入或惡意嘗試,可能耗盡驗證伺服器的 CPU、記憶體或工作佇列。
NIST SP 800-63B-4 的原則,是在驗證服務可承受的前提下選擇較高的成本,並隨運算能力提升逐步調整。像 OWASP 對 Argon2id 就列出過一組最低建議配置(記憶體成本、迭代次數與平行度),但那是參考最低標準,不是所有系統的最佳解。OWASP Password Storage Cheat Sheet 也提醒,理想參數需要在實際部署環境中測試,而不是直接複製別人的數字。
在使用 Session 或 OAuth Token 的架構中,密碼驗證應放在需要它的登入流程;後續 API 則依 Session 或 Token 的規則驗證請求,不必每次都要求使用者重新送出密碼並執行密碼雜湊。
真正的優化,是把不同成本放在正確的流程階段,而不是把必要的保護拿掉。
使用者登入或完成授權之後,API 仍然需要判斷每次請求帶來的 Access Token 是否可以接受。這時常見有兩種處理方式。
如果 Access Token 是可驗證的 JWT,Resource Server 可以使用可信的金鑰,檢查簽章與必要欄位,通常不必為了每次 Token 驗證都呼叫 Authorization Server。
它能降低網路往返與授權伺服器的負擔,但要注意:本地驗證通常確認的是這顆 Token 帶來的資訊,不是所有外部狀態的最新版本。
例如,Token 核發後,使用者的角色可能已經被移除。如果 API 只相信 Token 裡原本寫好的角色,就不會因為資料庫已更新,自動知道這項變更。
RFC 7662 定義的 Token Introspection,讓 Resource Server 透過受保護且經授權的查詢,向 Authorization Server 確認 Token 狀態與相關資訊。
查詢的回應裡有一個 active 欄位,代表 Authorization Server 現在認不認這顆 Token:是不是自己發的、有沒有過期、是不是已經被撤銷。
比起 API 自己檢查簽章,這樣拿到的狀態更接近當下狀態,但每次請求都多一趟網路查詢,也多依賴一個服務;若將回應快取以減少查詢次數,取得的狀態則會出現延遲,此部分將於後續說明。
| 比較項目 | 本地驗證 JWT | Token Introspection |
|---|---|---|
| 主要成本 | 本地密碼學運算與欄位檢查 | 網路往返、伺服器查詢與回應處理 |
| 對發行者服務的依賴 | 已取得可信金鑰時,通常不必每次連線;仍需處理金鑰更新 | 未使用可接受的快取結果時,需要取得查詢回應 |
| 狀態新鮮度 | 單靠 JWT 內容,無法自動得知核發後的撤銷或權限變更 | 依發行者掌握的最新狀態與快取策略而定 |
| 常見使用情境 | JWT 型 Access Token,可在 API 端驗證 | 常用於不透明 Token,也可以查詢支援此機制的 JWT |
這裡比較的是查核方式,不是兩種完全互斥的 Token 分類。JWT 是格式,Introspection 是查詢協定;是否能搭配,必須看 Authorization Server 的支援。
本地驗證並非僅將 JWT 解碼為 JSON。不論採用哪一種查核方式,都可以回頭確認幾件事:這顆 Token 是否來自允許的發行來源?演算法、有效期限與受眾是否已經檢查?而在接受這顆 Token 之後,該主體是否確實被允許存取這筆資源?RFC 8725 針對演算法、Issuer、Audience 與不同 JWT 用途均提出明確建議。需要注意的是,Token 狀態與資源授權屬於不同層次的判斷。active: true 只表示這顆 Token 本身仍可接受,不代表這次操作已經獲准;使用者的 Token 雖未過期也未被撤銷,仍須再判斷他能否讀取這份資料或執行該項管理操作。
縮短 Access Token 的有效期限可以限制外洩後的使用窗口,但也會增加刷新次數與 Authorization Server 的負載,而且 Refresh Token 本身仍需保護。另外,登出、停用帳號或撤銷 Refresh Token,不代表已核發的 JWT Access Token 立即失效;若業務要求快速撤權,必須另外規劃 Resource Server 如何取得並執行這項變更。
所以這裡並沒有哪一種天生比較好,而是取決於兩項評估:該系統對狀態新鮮度的要求,以及可承受的查詢成本。
快取不只是把資料放到 Redis 或記憶體裡。放在驗證與授權流程中,它代表:系統願意在一段時間內,繼續相信先前取得的資訊。
因此這裡涉及兩個層面:快取的對象為何,以及快取的有效期間應該多長。
| 快取對象 | 可節省的成本 | 必須處理的風險 |
|---|---|---|
| JWKS 公開金鑰與解析後的金鑰物件 | 重複下載與處理驗證金鑰 | 金鑰輪替、來源信任與緊急失效 |
| Introspection 回應 | Token 狀態查詢與網路往返 | Token 已撤銷,API 卻仍使用舊的有效結果 |
| 權限資料或授權結果 | 資料庫、政策或關係查詢 | 角色、資源關係或環境條件已改變 |
| 敏感 API 回應 | 重複查詢與組合資料的成本 | 不同使用者或租戶之間共用到不該共用的資料 |
JWKS(JSON Web Key Set)是金鑰集合的表示格式;在這裡,指發行者提供、用來驗證 Token 簽章的公開金鑰集合。
以非對稱式簽章的 JWT 為例,一種做法是快取發行者的公開金鑰;AWS Cognito 官方文件也建議快取驗證金鑰,並處理定期更新與金鑰輪替。
一旦採用快取,隨之而來的是幾項需要自行決定的問題:金鑰應從何種來源取得才具可信度、金鑰輪替或出現未知 kid 時是否重新下載、下載失敗時應拒絕請求或仍予放行,以及過去的驗證成功紀錄是否能作為本次驗證的依據。
假設 API 在 12:00 查到 active: true 並快取兩分鐘,而 Authorization Server 在 12:01 撤銷了這顆 Token,那麼在快取到期前,API 仍可能繼續接受請求。
RFC 7662 明確指出,快取 Introspection 回應屬於效能與資訊新鮮度之間的取捨;若回應包含 exp,快取時間也不得超過該時限。
因此,TTL 的設定實際上是一項業務問題:該系統可以容忍已撤銷的權限繼續生效多久。若帳號同步、Token 狀態與權限資料各自存在延遲,需評估的也不是單一層的 TTL,而是整體流程的生效時間。
授權結果不只取決於帳號,還可能與角色、租戶、資源、操作、關係及環境條件相關。若某次判斷的前提是「使用者可讀取自己的薪資」,卻僅以使用者 ID 將結果快取為 Allow,則在讀取他人薪資時可能誤用該結果。
因此,授權快取的索引應包含哪些條件,以及權限或政策變更時如何使快取失效,實際上比快取期間的長短更值得先行釐清。
針對薪資明細等敏感回應,可依實際需求採用以下標頭,要求 HTTP 快取不得保存回應內容:
Cache-Control: no-store
需要注意的是,no-store、no-cache 與 private 的語意並不相同,且這些標頭僅適用於 HTTP 快取,並不涵蓋應用程式內部的 Redis、資料庫或 Log。即使已設定相關標頭,後端的存取控制與敏感資料遮罩仍須另行處理。
⚠️Replay Cache 雖然叫快取,但不是為了加速而存在。
它記錄的是「哪些一次性憑證已經被用過」,屬於安全狀態。若為了釋放記憶體而提早刪除這些紀錄,或在儲存服務故障時直接視為「未曾使用」而予以放行,同一份請求就可能被重複送出並通過驗證。
密碼雜湊提高了猜測的難度,但若攻擊者可以不受限制地呼叫登入端點,這些昂貴的運算反而可能被用來耗盡服務資源。Rate Limiting(限流)是指限制一定時間內可執行的請求或操作次數,既避免少數來源佔用過多資源,也降低線上密碼猜測與驗證碼濫用的機會。惟各端點的成本與風險並不相同,「所有 API 每分鐘幾次」這類單一設定是否能對應實際風險,仍有討論空間。
| 控制範圍 | 適合處理的問題 | 注意事項 |
|---|---|---|
| 來源 IP、連線與入口流量 | 大量無效請求、連線或異常輸入 | 共用出口可能誤傷正常使用者,分散來源也可能繞過單一 IP 限制 |
| 目標帳號的登入失敗次數 | 多個來源集中猜同一個帳號 | 避免被利用來永久鎖住別人的帳號 |
| 已驗證的使用者、Client 或租戶 | API 配額與公平使用 | 不能只相信外部自行填寫的識別碼,或未驗證 JWT 裡的欄位 |
| 特定高成本操作 | 匯出、複雜查詢、OTP/簡訊寄送 | 同時限制操作頻率、併發量與實際資源成本 |
若僅以來源 IP 計算登入失敗次數,攻擊者只要更換 IP 就可以繼續嘗試,因此 OWASP Authentication Cheat Sheet 建議失敗計數也要依帳號計算。但該文件同時提醒,以帳號為單位的鎖定機制可能被刻意觸發:攻擊者持續輸錯某個帳號的密碼,就能把這個帳號鎖住,讓本人也登不進去。
兩者合看,需要自行權衡的是:錯幾次才要處理、冷卻多久,以及鎖定後要用什麼方式解除,才能擋住密碼猜測,又不至於把帳號本人擋在外面。另一個容易被忽略的是配額的計數單位:若「每分鐘幾次」是綁在單一 Access Token 上,使用者只要重新登入換一顆新 Token,計數是不是就歸零了?
只查詢自己的基本資料,與匯出整個部門的薪資,雖然都算一次 HTTP 請求,但成本卻可能差很多。
OWASP API Security Top 10 的 API4:2023 把這類風險整理為 Unrestricted Resource Consumption(資源消耗未受限制)。所以除了請求頻率,也值得回頭看看:單次請求能讀多少資料、工作可以跑多久、同時能跑幾個,以及簡訊或第三方服務的用量與費用由誰把關。已經通過驗證,不代表資源使用可以沒有上限。
以「每分鐘最多 100 次」為例,若採用整分鐘重新計數的固定窗口,同一來源可以在窗口結束前送完 100 次,下一秒進入新窗口再送 100 次;這種方式實作簡單,卻可能在窗口交界出現集中突發。滑動窗口改以「過去一分鐘」計算,Token Bucket 則讓額度依固定速率逐步補充,兩者都是在處理同一個問題,適用與否仍需依實際負載型態評估。
分散部署時還有另一層問題:若三台 API Server 各自在本機計數,設定上雖為 100 次,實際可能放行到 300 次。若需要精確的配額,就必須將計數改放在共享儲存,並以原子性操作更新,避免多台同時讀到相同數值,代價則是每次請求都多一趟網路成本。
常見的做法是在入口層先擋下明顯過量的流量,再由應用層依使用者與操作成本進一步區分。不過配額多放行一兩次通常影響有限,Authorization Code 與 OTP 則只能使用一次,多放行一次就等於留下重放的空間;這類一次性規則能否沿用相同的誤差容忍度,屬於另一層需要釐清的問題。
RFC 6585 定義了 429 Too Many Requests,並允許使用 Retry-After 告知 Client 等待時間。例如:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
Cache-Control: no-store
{
"error": "rate_limit_exceeded",
"message": "請稍後再試。"
}
這裡的 30 秒只是示意,實際值取決於限流策略與額度恢復的情況。另一個連帶的問題是 Client 的行為,若所有請求在同一刻重送,服務可能在剛恢復時又被壓垮。
限流是在控制使用量,不是用來判斷請求本身是否有權限。請求就算沒有超額,仍然需要通過正常的驗證與授權。
安全與效能的取捨,不是把防護一項一項刪掉,而是理解每項機制的目的,再決定它放在哪裡、使用多少資源,以及多久需要重新確認。本篇可以整理成四個重點:
若將上述機制放回完整流程,一般受保護 API 的處理順序可以理解為:入口流量控制與輸入限制 → 驗證憑證、時間、用途與必要的持有證明 → 依已驗證主體檢查配額 → 針對本次操作與資源執行授權 → 執行業務邏輯並套用回應快取政策。實際順序可依架構調整,但不宜使「快取命中」「來自內網」或「之前登入成功」成為省略後續判斷的依據。
在優化對象的識別上,實際量測通常比推測可靠:API 效能下降未必來自密碼學運算,也可能源於重複下載 JWKS、重複查詢權限或工作佇列累積。
會開始整理這一系列,是因為在學習身分驗證與授權相關技術的過程中,我逐漸發現,許多概念單獨理解並不困難,但當它們同時出現在登入、授權與 API 存取流程中時,彼此之間的角色與界線往往容易混淆。
因此,這三十天的內容從最基本的概念出發,逐步延伸至 Session、Token、OAuth 2.0、OIDC、SSO、Passkey,以及不同的存取控制模型。希望不只是介紹各項技術本身,更能釐清它們存在的目的、所解決的問題、適用的情境,以及各自的限制。
如果這一系列能讓讀者在閱讀 OAuth、OIDC、Token、Passkey 或相關技術文件時,對整體脈絡有更清晰的理解,並在實際進行系統設計時,能更有意識地思考驗證、授權與權限檢查之間的關係,那麼這三十天的整理便具有了意義。
最後,感謝每一位閱讀至此的讀者,也感謝這段整理與學習的過程。
謝謝你陪伴這個系列走到最後。